/**
  main.c - Created by Timothy Morey on 4/4/2012.

  This file contains the entry point into the nvn-display application, which
  manages a single display.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef __WINDOWS__
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>
#include <WinSock2.h>
#include <GL/glew.h>
#else
#include <unistd.h>
#endif

// We must be sure to include gl.h AFTER glew.h
#include <GL/gl.h>

#include <cl-args.h>
#include <data-source.h>
#include <err.h>
#include <frame-buffer.h>
#include <logger.h>
#include <messages.h>
#include <renderer.h>
#include <server.h>
#include <window.h>

#ifdef __WINDOWS__
#pragma warning(disable:4996)
#endif

int g_Layer = INVALID_LAYER;
int g_WindowHandle;

int Init();
int ProcessCLArgs(int argc, char* argv[], 
                  int* chattyport, int* silent,
                  int* x, int* y, int* width, int* height, int* borderless);
int ChattyMessageReceived(Server* server, const char* clientName, 
                          const char* recvBuf, int recvLen, 
                          char* replyBuf, int* replyLen);
void Shutdown();

int main(int argc, char* argv[])
{
  int chattyport = 16661;
  int x = 100;
  int y = 100;
  int width = 640;
  int height = 480;
  int borderless = 0;
  char logmsg[256];
  char logfile[32];
  int pid = 0;
  int silent = 0;
  char temp[32];
  int result = NVN_ERROR;
  Server* controlserver = 0;

#ifdef __WINDOWS__
  pid = (int)GetCurrentProcessId();
#else
  pid = (int)getpid();
#endif

  sprintf(logfile, "nvn-display.%d.out", pid);
  InitLogger(logfile, 
             LogLevelDebug | LogLevelInfo | LogLevelError,
             LogLevelMute, LogLevelMute);

  if(NVN_NOERR == ProcessCLArgs(argc, argv, &chattyport, &silent,
                                &x, &y, &width, &height, &borderless))
  {
    if(!silent)
    {
      ChangeLogLevels(LogLevelDebug | LogLevelInfo | LogLevelError,
                      LogLevelDebug | LogLevelInfo,
                      LogLevelError);
    }

    if(NVN_NOERR == Init())
    {
      sprintf(temp, "nvn-display (%d)", pid);
      CreateGLWindow(temp, x, y, width, height, borderless, &g_WindowHandle);
      

      while(chattyport < 16761 && result != NVN_NOERR)
      {
        sprintf(logmsg, 
                "Attempting to start control server on port %d...", chattyport);
        WriteMessage(LogLevelDebug, logmsg);
        
        result = StartAsyncServer(chattyport, 
                                  ChattyMessageReceived, 
                                  ServerModeChatty, 
                                  &controlserver);
        
        if(result != NVN_NOERR)
          chattyport++;
      }

      if(result == NVN_NOERR)
      {
        printf("Ready -- chattyport=%d\n", chattyport);

        // We need to flush to make sure python gets the above message, in case
        // this process was started as a python subprocess.
        fflush(stdout);

        WaitAsyncServer(controlserver);
      }

      DestroyGLWindow(g_WindowHandle);
    }
    else
    {
      WriteMessage(LogLevelError, "Initialization error");
    }
  }

  Shutdown();
  printf("Exiting...\n");

  return 0;
}

int Init()
{
  int retval = NVN_NOERR;

#ifdef __WINDOWS__
  /* It is necessary to initialize Winsock once for the process. */
  if(NVN_NOERR == retval)
  {
    WSADATA wsaData;
    if(0 != WSAStartup(MAKEWORD(2, 2), &wsaData))
      retval = NVN_EWINSOCKFAIL;
  }
#endif

  if(NVN_NOERR == retval)
  {
    retval = StartUIThread();
  }

  return retval;
}

int ProcessCLArgs(int argc, char* argv[], 
                  int* chattyport, int* silent,
                  int* x, int* y, int* width, int* height, int* borderless)
{
  int retval = NVN_NOERR;
  ArgDef args[] = {
    { "chattyport", 'c', 1, "The port used for chatty control communications "
                            "with the display" },
    { "silent",     's', 0, "Indicates that the process should run in silent "
                            "mode and only write minimal information to stdout/"
                            "stderr" },
    { "x",           0,  1, "Initial display x-position" },
    { "y",           0,  1, "Initial display y-position" },
    { "width",       0,  1, "Initial display width" },
    { "height",      0,  1, "Initial display height" },
    { "borderless",  0,  0, "Indicates that the window should be borderless" },
    { "help",       'h', 0, "Displays information about invoking this application" }
  };
  
  CLSchema schema = { args, 7 };
  int valid, contains;
  const char* value;
  
  if(NVN_NOERR == ValidateArgs(argc, argv, &schema, &valid) && valid)
  {
    if(NVN_NOERR == ContainsArg(argc, argv, "help", &schema, &contains) && contains)
    {
      PrintArgSchema(argc, argv, &schema);
      retval = NVN_EUSERCANCELED;
    }
    
    if(NVN_NOERR == ContainsArg(argc, argv, "chattyport", &schema, &contains) && contains)
    {
      GetArgValue(argc, argv, "chattyport", &schema, &value);
      *chattyport = atoi(value);
    }

    if(NVN_NOERR == ContainsArg(argc, argv, "silent", &schema, &contains))
    {
      *silent = contains;
    }

    if(NVN_NOERR == ContainsArg(argc, argv, "x", &schema, &contains) && contains)
    {
      GetArgValue(argc, argv, "x", &schema, &value);
      *x = atoi(value);
    }

    if(NVN_NOERR == ContainsArg(argc, argv, "y", &schema, &contains) && contains)
    {
      GetArgValue(argc, argv, "y", &schema, &value);
      *y = atoi(value);
    }

    if(NVN_NOERR == ContainsArg(argc, argv, "width", &schema, &contains) && contains)
    {
      GetArgValue(argc, argv, "width", &schema, &value);
      *width = atoi(value);
    }

    if(NVN_NOERR == ContainsArg(argc, argv, "height", &schema, &contains) && contains)
    {
      GetArgValue(argc, argv, "height", &schema, &value);
      *height = atoi(value);
    }

    if(NVN_NOERR == ContainsArg(argc, argv, "borderless", &schema, &contains))
    {
      *borderless = contains;
    }
  }
  else
  {
    WriteMessage(LogLevelError, "Invalid command line arguments");
    retval = NVN_EINVCLARGS;
  }
  
  return retval;
}

int ChattyMessageReceived(Server* server, const char* clientName, 
                          const char* recvbuf, int recvlen, 
                          char* replybuf, int* replylen)
{
  int retval = NVN_NOERR;
  MessageType type = MessageTypeUnknown;
  char msgName[32];
  char temp[32];
	char logmsg[4096];

	sprintf(logmsg, "ChattyMessageReceived from %s:\n%s", clientName, recvbuf);
	WriteMessage(LogLevelDebug, logmsg);

  if(NVN_NOERR == GetMessageInfo(recvbuf, recvlen, &type, msgName, 32))
  {
    if(0 == strcmp("AddLayer", msgName))
    {
      LayerProperties props;

			WriteMessage(LogLevelDebug, "Processing AddLayer request");
      ParseAddLayerRequest(recvbuf, recvlen, &props);
      CreateLayer(props, &g_Layer);
      AddLayer(g_WindowHandle, g_Layer);
      CreateSimpleResponse(1, msgName, "Success", replybuf, replylen);
			WriteMessage(LogLevelDebug, "Finished processing AddLayer request");
    }
    else if(0 == strcmp("AdjustDisplay", msgName))
    {
      int x, y, width, height, borderless;

			WriteMessage(LogLevelDebug, "Processing AdjustDisplay request");
      ParseAdjustDisplayRequest(recvbuf, recvlen, &x, &y, &width, &height, &borderless);
      DestroyGLWindow(g_WindowHandle);
      CreateGLWindow("title", x, y, width, height, borderless, &g_WindowHandle);
      AddLayer(g_WindowHandle, g_Layer);
      CreateSimpleResponse(1, msgName, "Success", replybuf, replylen);
			WriteMessage(LogLevelDebug, "Finished processing AdjustDisplay request");
    }
    else if(0 == strcmp("CloseDisplay", msgName))
    {
      WriteMessage(LogLevelDebug, "Processing CloseDisplay request");
      StopServer(server);
      WriteMessage(LogLevelDebug, "StopServer command issued");
      CreateSimpleResponse(1, msgName, "Success", replybuf, replylen);
			WriteMessage(LogLevelDebug, "Finished processing CloseDisplay request");
    }
    else if(0 == strcmp("GetFrameCount", msgName))
    {
      int count = 0;
			WriteMessage(LogLevelDebug, "Processing GetFrameCount request");
      GetLayerFrameCount(g_Layer, &count);
      sprintf(temp, "%d", count);
      CreateSimpleResponse(1, msgName, temp, replybuf, replylen);
			WriteMessage(LogLevelDebug, "Finished processing GetFrameCount request");
    }
    else if(0 == strcmp("SetCurrentFrame", msgName))
    {
      int frame = 0;
			WriteMessage(LogLevelDebug, "Processing SetCurrentFrame request");
      ParseSimpleRequest(recvbuf, recvlen, msgName, 32, temp, 32);
      frame = atoi(temp);
      SetLayerCurrentFrame(g_Layer, frame);
      RedrawGLWindow(g_WindowHandle);
      CreateSimpleResponse(1, msgName, "Success", replybuf, replylen);
			WriteMessage(LogLevelDebug, "Finished processing SetCurrentFrame request");
    }
		else
		{
			sprintf(logmsg, "Received unrecognized message type: %s", msgName);
			WriteMessage(LogLevelDebug, logmsg);
			retval = NVN_ERROR;
			*replylen = 0;
		}
  }
	else
	{
		sprintf(logmsg, "Unable to interpret message: %s", recvbuf);
		WriteMessage(LogLevelDebug, logmsg);
		retval = NVN_ERROR;
		*replylen = 0;
	}

	if(NVN_NOERR == retval && *replylen > 0)
	{
		sprintf(logmsg, "Sending response:\n%s", replybuf);
		WriteMessage(LogLevelDebug, logmsg);
	}

  return retval;
}

void Shutdown()
{
  StopUIThread();

#ifdef __WINDOWS__
  /* Shutdown Winsock. */
  WSACleanup();
#endif
}
